-
-
Notifications
You must be signed in to change notification settings - Fork 11
Tube gyro pir #124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Tube gyro pir #124
Conversation
pio.ini: add MPU6050 driver backend ======= - NodeManager: send onUpdate to node when on/off changed - Nodes: add sharedData.gravity, add D_MPU6050.h - Module Drivers: add D_MPU6050.h - D_MPU6050.h: New - Effects: particles: add gyro
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds MPU6050 IMU support, exposes gravity in shared data, registers IMU driver and triggers per-node init under mutex, removes compile-time gyro guards in effects, adds I2C initialization and device scanning, and updates platformio dependencies. Changes
Sequence DiagramsequenceDiagram
participant NM as NodeManager
participant MD as ModuleDrivers
participant IMU as IMUDriver
participant I2C as I2C Bus
participant SD as SharedData
participant Effect as E_MoonLight
NM->>MD: request node allocation (node.on = true)
MD->>IMU: instantiate IMUDriver
NM->>IMU: nodeClass->onUpdate(oldValue, nodeState)
IMU->>I2C: init / probe MPU6050 (Wire)
I2C-->>IMU: ACK / device bytes
IMU->>IMU: DMP init & calibration
loop every 20ms
IMU->>I2C: read FIFO packet
I2C-->>IMU: sensor packet
IMU->>IMU: compute quaternion → gravity & euler
IMU->>SD: update sharedData.gravity
end
Effect->>SD: read sharedData.gravity
SD-->>Effect: gravity vector
Effect->>Effect: apply gravity to rendering
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@src/MoonBase/NodeManager.h`:
- Line 267: The call to nodeClass->onUpdate currently passes the full nodeState
without holding layerMutex and thus is inconsistent with the controls-value
path; wrap the on-toggle path's nodeClass->onUpdate call with layerMutex (same
scope used in the controls-value path) and normalize the argument semantics by
passing a consistent control object (e.g., construct a single-control JSON
containing only the on field or an explicit {"on": ...} object) instead of the
full nodeState so onUpdate implementations always receive the same JSON shape.
In `@src/MoonLight/Nodes/Drivers/D_MPU6050.h`:
- Line 46: The onUpdate method declaration lacks the override specifier which
risks silent signature drift; update the declaration of void onUpdate(const
Char<20>& oldValue, const JsonObject& control) in class D_MPU6050 to add the
override keyword so it matches the other overridden methods (e.g., setup,
hasOnLayout, onLayout, loop20ms) and lets the compiler validate the override.
- Around line 37-44: The method initI2S incorrectly initializes I2C (Wire.begin)
so rename the function to initI2C (and update all callers/usages) and update the
log message (EXT_LOGI) to reflect "initI2C" to avoid protocol confusion; while
here also remove or replace the hardcoded pins (Wire.begin(5, 6)) by using the
existing pin allocation mechanism or configurable pin variables (e.g., use
allocatePin or pass sda/scl parameters or read configured pins) so the function
no longer hardcodes 5 and 6 and will work across boards.
- Around line 83-84: The hasOnLayout() override currently returns true while
onLayout() is an empty override, causing unnecessary mapping-system calls;
either remove both overrides (delete the hasOnLayout() and onLayout() methods so
the node uses the base-class default of no layout handling) or implement real
layout initialization/update logic inside onLayout() and keep hasOnLayout()
true; locate the D_MPU6050 class methods hasOnLayout() and onLayout() and apply
one of these two changes (prefer removing both overrides if the driver does not
need to react to layout changes).
🧹 Nitpick comments (3)
src/MoonLight/Nodes/Drivers/D_MPU6050.h (2)
26-26: Dead code:gravityVector(public) anddevStatus(private member) are unused.
gravityVector(line 26) is declared but never written or read. The privategravity(line 135) is the actual gravity vector used inloop20ms. Either removegravityVectoror use it.- The member
devStatus(line 127) is shadowed by a local variable inonUpdate(line 56) and never referenced elsewhere.Remove dead declarations
Coord3D gyro; // in degrees (not radians) Coord3D accell; - VectorFloat gravityVector;private: MPU6050 mpu; // MPU control/status vars - uint8_t devStatus; // return status after each device operation (0 = success, !0 = error) uint8_t fifoBuffer[64]; // FIFO storage bufferAlso applies to: 127-127
46-81: No handling when node is turned off — MPU6050 DMP stays enabled.
onUpdateinitializes and enables the DMP whencontrol["on"] == true, but there's no branch forcontrol["on"] == false. When the user disables this driver node, the DMP keeps running andloop20msjust returns early due tomotionTrackingReadystayingtrue. Consider disabling DMP and resettingmotionTrackingReadyon turn-off to save power/bus bandwidth and zero outsharedData.gravity.Proposed off-handling
void onUpdate(const Char<20>& oldValue, const JsonObject& control) override { if (!control["on"].isNull()) { if (control["on"] == true) { if (initI2C()) { // ... existing init code ... } + } else { + // Disable DMP and reset state + if (motionTrackingReady) { + mpu.setDMPEnabled(false); + motionTrackingReady = false; + sharedData.gravity = Coord3D(); + } } } }platformio.ini (1)
144-145: MPU6050 dependency is added to[env](global) — consider scoping to[moonlight]if only MoonLight builds use it.Placing
ElectronicCats/MPU6050 @ 1.3.0under[env]lib_depsmeans it's pulled and linked for every board variant, even those without MPU6050 hardware or theFT_MOONLIGHTfeature flag. If this library is only used in MoonLight builds, moving it to the[moonlight]section (as done with FastLED and other specialized dependencies) would avoid unnecessary binary bloat for other firmware variants.Also note: line 144 uses 2 spaces for indentation while line 143 uses a tab — consider standardizing to match surrounding entries.
| if (nodeClass != nullptr) { | ||
| nodeClass->on = updatedItem.value.as<bool>(); // set nodeclass on/off | ||
| // EXT_LOGD(ML_TAG, " nodeclass 🔘:%d 🚥:%d 💎:%d", nodeClass->on, nodeClass->hasOnLayout(), nodeClass->hasModifier()); | ||
| nodeClass->onUpdate(updatedItem.oldValue, nodeState); // custom onUpdate for the node |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent mutex handling and argument semantics for onUpdate.
Two observations:
- No mutex: The controls-value path (line 282–285) holds
layerMutexaroundonUpdate, but this newon-toggle path does not. For MPU6050Driver the hardware init may not need it, but other nodes'onUpdatecould mutate shared layer state. Consider wrapping consistently:
+ xSemaphoreTake(*nodeClass->layerMutex, portMAX_DELAY);
nodeClass->onUpdate(updatedItem.oldValue, nodeState); // custom onUpdate for the node
+ xSemaphoreGive(*nodeClass->layerMutex);- Argument type mismatch: Here
nodeState(full node JSON withname,on,controls) is passed as thecontrolparameter, while at line 284 a single control object is passed. The MPU6050Driver relies on checkingcontrol["on"]to distinguish, but this convention is implicit and fragile — a future node'sonUpdatecould misinterpret the JSON shape.
🤖 Prompt for AI Agents
In `@src/MoonBase/NodeManager.h` at line 267, The call to nodeClass->onUpdate
currently passes the full nodeState without holding layerMutex and thus is
inconsistent with the controls-value path; wrap the on-toggle path's
nodeClass->onUpdate call with layerMutex (same scope used in the controls-value
path) and normalize the argument semantics by passing a consistent control
object (e.g., construct a single-control JSON containing only the on field or an
explicit {"on": ...} object) instead of the full nodeState so onUpdate
implementations always receive the same JSON shape.
| bool initI2S() { | ||
| // tbd: set pins in ui!! | ||
| // allocatePin(21, "Pins", "I2S SDA"); | ||
| // allocatePin(22, "Pins", "I2S SCL"); | ||
| bool success = Wire.begin(5, 6); | ||
| EXT_LOGI(ML_TAG, "initI2S Wire begin %s", success ? "success" : "failure"); | ||
| return success; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method is named initI2S but initializes I2C (Wire/Wire.begin).
I²S (Inter-IC Sound) and I²C (Inter-Integrated Circuit / Wire) are different protocols. This should be initI2C to avoid confusion.
Also, I2C pins 5 and 6 are hardcoded — acknowledged by the TODO, but worth noting this will fail on many boards.
Proposed rename
- bool initI2S() {
- // tbd: set pins in ui!!
- // allocatePin(21, "Pins", "I2S SDA");
- // allocatePin(22, "Pins", "I2S SCL");
+ bool initI2C() {
+ // tbd: set pins in ui!!
+ // allocatePin(21, "Pins", "I2C SDA");
+ // allocatePin(22, "Pins", "I2C SCL");
bool success = Wire.begin(5, 6);
- EXT_LOGI(ML_TAG, "initI2S Wire begin %s", success ? "success" : "failure");
+ EXT_LOGI(ML_TAG, "initI2C Wire begin %s", success ? "success" : "failure");
return success;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| bool initI2S() { | |
| // tbd: set pins in ui!! | |
| // allocatePin(21, "Pins", "I2S SDA"); | |
| // allocatePin(22, "Pins", "I2S SCL"); | |
| bool success = Wire.begin(5, 6); | |
| EXT_LOGI(ML_TAG, "initI2S Wire begin %s", success ? "success" : "failure"); | |
| return success; | |
| } | |
| bool initI2C() { | |
| // tbd: set pins in ui!! | |
| // allocatePin(21, "Pins", "I2C SDA"); | |
| // allocatePin(22, "Pins", "I2C SCL"); | |
| bool success = Wire.begin(5, 6); | |
| EXT_LOGI(ML_TAG, "initI2C Wire begin %s", success ? "success" : "failure"); | |
| return success; | |
| } |
🤖 Prompt for AI Agents
In `@src/MoonLight/Nodes/Drivers/D_MPU6050.h` around lines 37 - 44, The method
initI2S incorrectly initializes I2C (Wire.begin) so rename the function to
initI2C (and update all callers/usages) and update the log message (EXT_LOGI) to
reflect "initI2C" to avoid protocol confusion; while here also remove or replace
the hardcoded pins (Wire.begin(5, 6)) by using the existing pin allocation
mechanism or configurable pin variables (e.g., use allocatePin or pass sda/scl
parameters or read configured pins) so the function no longer hardcodes 5 and 6
and will work across boards.
| return success; | ||
| } | ||
|
|
||
| void onUpdate(const Char<20>& oldValue, const JsonObject& control) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Missing override keyword on onUpdate.
All other overridden methods in this class (setup, hasOnLayout, onLayout, loop20ms, destructor) use override, but onUpdate does not. Without override, the compiler won't catch signature drift if the base class changes.
Fix
- void onUpdate(const Char<20>& oldValue, const JsonObject& control) {
+ void onUpdate(const Char<20>& oldValue, const JsonObject& control) override {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| void onUpdate(const Char<20>& oldValue, const JsonObject& control) { | |
| void onUpdate(const Char<20>& oldValue, const JsonObject& control) override { |
🤖 Prompt for AI Agents
In `@src/MoonLight/Nodes/Drivers/D_MPU6050.h` at line 46, The onUpdate method
declaration lacks the override specifier which risks silent signature drift;
update the declaration of void onUpdate(const Char<20>& oldValue, const
JsonObject& control) in class D_MPU6050 to add the override keyword so it
matches the other overridden methods (e.g., setup, hasOnLayout, onLayout,
loop20ms) and lets the compiler validate the override.
|
@coderabbitai, the mcu6050 is not working, I wired the scanner/scl/gnd/vcc correctly. got the following logging, what is wrong? [ 56027][D][NodeManager.h:262] onUpdate(): [💫] httpd (1524) MPU6050 driver ☸️ on: true (#2) ransmit_receive failed: [259] ESP_ERR_INVALID_STATE |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 6271
The ProblemWhen The SolutionYou need to end the existing I2C bus before reinitializing it with your custom pins. Modify the bool initI2S() {
// tbd: set pins in ui!!
// allocatePin(21, "Pins", "I2S SDA");
// allocatePin(22, "Pins", "I2S SCL");
// End any existing I2C bus before reinitializing
Wire.end();
delay(10); // Small delay to ensure bus is fully released
bool success = Wire.begin(5, 6);
EXT_LOGI(ML_TAG, "initI2S Wire begin %s", success ? "success" : "failure");
return success;
}The Additional ChecksAlso verify:
Try the |
|
@coderabbitai, regarding
Improved it a bit but still errors, please investigate: [ 69729][D][NodeManager.h:262] onUpdate(): [💫] httpd (1524) MPU6050 driver ☸️ on: true (#2) |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 5209 🌐 Web query: 💡 Result:
Sources: Citations:
The Real ProblemBased on the logs and research, the issue is likely that ESP_ERR_INVALID_STATE occurs when the I2C driver isn't properly installed or was deleted/deinitialized. When you call Solution: Remove Wire.end() and Add Proper DelayTry this updated bool initI2S() {
// tbd: set pins in ui!!
// allocatePin(21, "Pins", "I2S SDA");
// allocatePin(22, "Pins", "I2S SCL");
// Don't call Wire.end() - it deletes the driver and causes INVALID_STATE
bool success = Wire.begin(5, 6);
EXT_LOGI(ML_TAG, "initI2S Wire begin %s", success ? "success" : "failure");
if (success) {
delay(100); // Give I2C bus time to stabilize
}
return success;
}Verify Hardware with I2C ScannerBefore testing again, let's verify the MPU6050 is actually present and at the correct address. Add this temporary diagnostic function: void scanI2C() {
EXT_LOGI(ML_TAG, "Scanning I2C bus...");
byte count = 0;
for (byte i = 1; i < 127; i++) {
Wire.beginTransmission(i);
if (Wire.endTransmission() == 0) {
EXT_LOGI(ML_TAG, "Found I2C device at address 0x%02X", i);
count++;
}
}
EXT_LOGI(ML_TAG, "Found %d device(s)", count);
}Call it in if (initI2S()) {
scanI2C(); // Diagnostic - remove after testing
mpu.initialize();
// ... rest of codeHardware ChecklistThe MPU6050 should appear at 0x68 or 0x69 (depending on AD0 pin). Verify:
Let me know what addresses the scanner finds! |
|
@coderabbitai, regarding
[ 18026][I][esp32-hal-i2c-ng.c:112] i2cInit(): Initializing I2C Master: num=0 sda=5 scl=6 freq=100000 |
|
Tip For best results, initiate chat on the files or code changes.
Critical Issue: GPIO Pin ChoiceESP32 allows any GPIO pin to be configured as I2C SDA and SCL, but the default I2C pins for ESP32 with Arduino IDE are GPIO 22 (SCL) and GPIO 21 (SDA). These are the most reliable pins for I2C communication. Immediate Solution: Use Standard I2C PinsChange your bool initI2C() {
// Use standard ESP32 I2C pins
// GPIO 21 = SDA, GPIO 22 = SCL
bool success = Wire.begin(21, 22);
EXT_LOGI(ML_TAG, "initI2C Wire begin %s", success ? "success" : "failure");
if (success) {
delay(100); // Give I2C bus time to stabilize
}
return success;
}Rewire your MPU6050:
Additional TroubleshootingIf the scanner still finds 0 devices after switching pins:
The fact that the scanner took 6+ seconds and then failed with Try the pin change first and let me know what the scanner finds! |
|
@coderabbitai, Which has pin 5 and 6, please check their website and let me know if 5,6 is indeed right |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🌐 Web query: 💡 Result: For the Seeed Studio XIAO ESP32S3 / XIAO ESP32S3 Sense, the default I2C pins are:
Note: the Sense camera module uses a different I2C pair:
Sources: [1] [2] [3] [4]
The Seeed XIAO ESP32S3 uses GPIO 5 (SDA) and GPIO 6 (SCL) as the default I2C pins, so your code is using the right pins. Since the pins are correct but the scanner found 0 devices, the issue is likely one of the following: Hardware Issues to Check
Software Issue: Arduino-ESP32 Core BugThe Try adding a small delay after bool initI2C() {
bool success = Wire.begin(5, 6); // Correct pins for XIAO ESP32S3
EXT_LOGI(ML_TAG, "initI2C Wire begin %s", success ? "success" : "failure");
if (success) {
delay(200); // Longer delay for I2C bus stabilization
Wire.setClock(100000); // Explicitly set to 100kHz
}
return success;
}What Arduino-ESP32 core version are you using? (Check |
|
This is my mcu6050 board , do I need to short wire anything? I am on arduino v3.3.5 |
|
Tip For best results, initiate chat on the files or code changes.
What I See in the ImageThe Comment image shows a blue GY-521 MPU6050 breakout board with:
The AD0 Pin - Important!Yes, you should connect the AD0 pin! Here's why: The AD0 pin controls the I2C address:
Right now, your AD0 pin appears to be floating (not connected), which can cause unreliable I2C communication. Solution: Connect the AD0 pin to GND for the standard 0x68 address. Arduino-ESP32 3.0.5 I2C IssuesYou're on Arduino-ESP32 3.0.5, which has known I2C bugs causing Updated Code to TryUpdate your bool initI2C() {
bool success = Wire.begin(5, 6); // XIAO ESP32S3 I2C pins
EXT_LOGI(ML_TAG, "initI2C Wire begin %s", success ? "success" : "failure");
if (success) {
Wire.setClock(50000); // Lower clock speed to 50kHz for stability
delay(200); // Longer stabilization delay
}
return success;
}
void onUpdate(const Char<20>& oldValue, const JsonObject& control) {
EXT_LOGI(ML_TAG, "onUpdate %s", oldValue.charArr);
if (control["n"].as<Char<20>>() == "on" && nodeState[control["n"].as<Char<20>>()].as<bool>()) {
if (initI2C()) {
scanI2C(); // Keep the scanner for now
// Explicitly set MPU6050 address to 0x68
mpu.setAddr(MPU6050_ADDRESS_AD0_LOW); // or just use default
mpu.initialize();
delay(100);
if (mpu.testConnection()) {
EXT_LOGI(ML_TAG, "MPU6050 connection successful at address 0x68");
// ... rest of DMP setup
} else {
EXT_LOGI(ML_TAG, "MPU6050 connection failed - check AD0 wiring");
}
}
}
}Wiring ChecklistXIAO ESP32S3 → MPU6050:
Next Steps
If it still doesn't work after grounding AD0, we may need to try Arduino-ESP32 v2.x or use a workaround for the 3.x I2C driver bug. Let me know what the scanner shows after connecting AD0! |
Change MPU6050 driver to IMU driver and add board control (MPU6050 and BMI160) I2S -< I2C, including safety delays - WIP add scanI2C
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In `@src/MoonBase/Nodes.h`:
- Around line 347-348: The field Coord3D gravity likely uses integer components
and will truncate the MPU6050 float gravity vector; update the type or handling:
replace Coord3D gravity with a float-based struct (e.g., VectorFloat or a new
Coord3DFloat) or store gravity as an array of floats and ensure any code using
gravity performs the proper float reads/conversions (see related D_IMU.h
handling), or if you must keep Coord3D, add clear documentation and explicit
scaling/quantization conversions where gravity is assigned so values in
[−1.0,1.0] are preserved correctly.
In `@src/MoonLight/Modules/ModuleDrivers.h`:
- Line 109: The build fails because the code calls getNameAndTags<IMUDriver>()
in addControlValue but the actual class in D_IMU.h is defined as MPU6050Driver;
update the template type references to the real class name (replace IMUDriver
with MPU6050Driver) wherever getNameAndTags<IMUDriver>() and related uses appear
(e.g., the addControlValue(control, getNameAndTags<...>()) calls around the IMU
driver registrations), or alternatively rename the class in D_IMU.h to
IMUDriver—ensure consistency between the class declaration (MPU6050Driver) and
all template/type usages (getNameAndTags and addControlValue).
In `@src/MoonLight/Nodes/Drivers/D_IMU.h`:
- Around line 41-56: In initI2C(), fix the comment typos (replace "I2S" with
"I2C"), remove the hardcoded Wire.begin(5, 6) and instead read user-configurable
I2C SDA/SCL pins (use the existing UI control pattern such as
addControl/allocatePin or the pin config variables you have) and pass those pin
values into Wire.begin, and correct the clock mismatch by either setting
Wire.setClock(100000) to match the "100kHz" comment or update the comment to
"50kHz" if 50000 is intended; ensure references to Wire.begin and Wire.setClock
and the initI2C() function are updated accordingly.
- Around line 71-112: onUpdate is performing long blocking initialization (delay
calls, mpu.dmpInitialize, CalibrateAccel, CalibrateGyro) on the HTTP/UI thread
and logging errors with EXT_LOGI; move the heavy work out of onUpdate into a
background task/thread or schedule a non-blocking state-machine so onUpdate only
enqueues/requests initialization and returns immediately, call a new InitIMUTask
(or similar) that runs initI2C(), scanI2C(), mpu.initialize(),
mpu.dmpInitialize(), CalibrateAccel(6), CalibrateGyro(6) and sets
motionTrackingReady when done; also change the two incorrect log calls that
currently use EXT_LOGI for failures (the mpu.dmpInitialize error and the
mpu.testConnection failure) to use EXT_LOGW or EXT_LOGE so errors are logged at
the proper severity.
- Around line 1-20: The class is named MPU6050Driver but other code expects
IMUDriver; rename the class MPU6050Driver to IMUDriver (and rename its
destructor ~MPU6050Driver to ~IMUDriver) so the symbol matches ModuleDrivers.h
references (IMUDriver), and update the file header `@file` from D_MPU6050.h to
D_IMU.h to reflect the real filename; alternatively you may add a type alias
(using IMUDriver = MPU6050Driver) but be sure the destructor/name usages match
across the module.
- Around line 126-153: The gravity VectorFloat components (gravity.x, gravity.y,
gravity.z) are being assigned to sharedData.gravity which is a Coord3D with int
fields, causing truncation; update the code so gravity is stored with proper
precision—either change the SharedData.gravity type from Coord3D (int) to a
float-based struct (e.g., VectorFloat/float fields) or scale the float values
before assignment (e.g., multiply gravity.x/y/z by a factor like 1000 and
convert to int) and document the unit change; locate assignments in the block
handling board == 0 (mpu.dmpGetGravity -> sharedData.gravity.x/y/z) and apply
the chosen fix consistently wherever sharedData.gravity is used.
🧹 Nitpick comments (2)
src/MoonLight/Nodes/Drivers/D_IMU.h (2)
86-86: LocaldevStatusshadows the private member on Line 162.Line 86 declares
uint8_t devStatusas a local variable, which shadows the privatedevStatusmember (Line 162). The private member is never used elsewhere. Either remove the private member or reuse it here by dropping the localuint8_tdeclaration.Proposed fix: remove unused private member
// MPU control/status vars - uint8_t devStatus; // return status after each device operation (0 = success, !0 = error) uint8_t fifoBuffer[64]; // FIFO storage buffer
156-156: Destructor doesn't clean up I2C or DMP state.When the driver node is removed, the destructor doesn't call
mpu.setDMPEnabled(false)orWire.end(). If the node is destroyed and recreated, this could leave the I2C bus in an inconsistent state — particularly relevant given the I2C initialization issues discussed in the PR comments.Proposed cleanup
- ~MPU6050Driver() override {}; // e.g. to free allocated memory + ~MPU6050Driver() override { + if (motionTrackingReady) { + mpu.setDMPEnabled(false); + } + }
|
|
||
| Coord3D gravity; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Coord3D gravity may not be the right type for gravity data.
As noted in the D_IMU.h review, the MPU6050 gravity vector contains float values in the range [–1.0, 1.0]. If Coord3D uses integer fields (e.g., int16_t), this will silently truncate to 0/±1. Consider using a float-based struct (e.g., VectorFloat) or documenting the expected scale/encoding if you intend to pre-scale the values.
🤖 Prompt for AI Agents
In `@src/MoonBase/Nodes.h` around lines 347 - 348, The field Coord3D gravity
likely uses integer components and will truncate the MPU6050 float gravity
vector; update the type or handling: replace Coord3D gravity with a float-based
struct (e.g., VectorFloat or a new Coord3DFloat) or store gravity as an array of
floats and ensure any code using gravity performs the proper float
reads/conversions (see related D_IMU.h handling), or if you must keep Coord3D,
add clear documentation and explicit scaling/quantization conversions where
gravity is assigned so values in [−1.0,1.0] are preserved correctly.
| addControlValue(control, getNameAndTags<ArtNetOutDriver>()); | ||
| addControlValue(control, getNameAndTags<AudioSyncDriver>()); | ||
| addControlValue(control, getNameAndTags<IRDriver>()); | ||
| addControlValue(control, getNameAndTags<IMUDriver>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Build failure: IMUDriver is not declared — class is named MPU6050Driver in D_IMU.h.
This is the downstream effect of the class name mismatch in src/MoonLight/Nodes/Drivers/D_IMU.h (Line 16), where the class is defined as MPU6050Driver. The same issue affects Line 147. Either rename the class in D_IMU.h to IMUDriver, or update both references here to MPU6050Driver.
🧰 Tools
🪛 GitHub Actions: PlatformIO CI
[error] 109-109: 'IMUDriver' was not declared in this scope; did you mean 'IRDriver'?
[error] 109-109: no matching function for call to 'getNameAndTags()' (template argument deduction/substitution failed)
🤖 Prompt for AI Agents
In `@src/MoonLight/Modules/ModuleDrivers.h` at line 109, The build fails because
the code calls getNameAndTags<IMUDriver>() in addControlValue but the actual
class in D_IMU.h is defined as MPU6050Driver; update the template type
references to the real class name (replace IMUDriver with MPU6050Driver)
wherever getNameAndTags<IMUDriver>() and related uses appear (e.g., the
addControlValue(control, getNameAndTags<...>()) calls around the IMU driver
registrations), or alternatively rename the class in D_IMU.h to IMUDriver—ensure
consistency between the class declaration (MPU6050Driver) and all template/type
usages (getNameAndTags and addControlValue).
src/MoonLight/Nodes/Drivers/D_IMU.h
Outdated
| bool initI2C() { | ||
| // tbd: set pins in ui!! | ||
| // allocatePin(21, "Pins", "I2S SDA"); | ||
| // allocatePin(22, "Pins", "I2S SCL"); | ||
| Wire.end(); | ||
| delay(10); | ||
| bool success = Wire.begin(5, 6); | ||
| EXT_LOGI(ML_TAG, "initI2C Wire begin %s", success ? "success" : "failure"); | ||
|
|
||
| if (success) { | ||
| delay(200); // Give I2C bus time to stabilize | ||
| Wire.setClock(50000); // Explicitly set to 100kHz | ||
| } | ||
|
|
||
| return success; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded I2C pins and comment/code mismatch on clock speed.
Three items here:
- Lines 43–44: Comments reference "I2S" — should be "I2C".
- Line 47:
Wire.begin(5, 6)hardcodes GPIO5/GPIO6 (XIAO ESP32S3). This will break on other boards. Consider making the pins configurable via UI controls (the commented-outaddControlapproach on lines 42–44 is the right direction). - Line 52: Comment says "Explicitly set to 100kHz" but the code sets
50000(50 kHz).
Fix the comment typos
- // tbd: set pins in ui!!
- // allocatePin(21, "Pins", "I2S SDA");
- // allocatePin(22, "Pins", "I2S SCL");
+ // tbd: set pins in ui!!
+ // allocatePin(21, "Pins", "I2C SDA");
+ // allocatePin(22, "Pins", "I2C SCL");- Wire.setClock(50000); // Explicitly set to 100kHz
+ Wire.setClock(50000); // Explicitly set to 50kHz (lower for reliability)🤖 Prompt for AI Agents
In `@src/MoonLight/Nodes/Drivers/D_IMU.h` around lines 41 - 56, In initI2C(), fix
the comment typos (replace "I2S" with "I2C"), remove the hardcoded Wire.begin(5,
6) and instead read user-configurable I2C SDA/SCL pins (use the existing UI
control pattern such as addControl/allocatePin or the pin config variables you
have) and pass those pin values into Wire.begin, and correct the clock mismatch
by either setting Wire.setClock(100000) to match the "100kHz" comment or update
the comment to "50kHz" if 50000 is intended; ensure references to Wire.begin and
Wire.setClock and the initI2C() function are updated accordingly.
ModuleIO - Include Wire - Add controls i2cFreq and i2cBus - default assign sda to pin 21 and scl to pin 22 - readPins: add I2C pin assignment - add updateDevices: Scanning I2C bus and send frequency and addresses to UI IMU driver - remove initI2C and scan I2C - gravity xyz float to int correction - WIP
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@src/MoonBase/Modules/ModuleIO.h`:
- Line 222: The i2cFreq control created via addControl(controls, "i2cFreq", ...)
uses a max of 65534 which is below typical Wire.getClock() values (100000,
400000); update the max bound on the "i2cFreq" control in ModuleIO.h to a larger
value (e.g., 1000000 or UINT32_MAX) so real I2C clock frequencies aren't
clamped, ensuring the chosen max fits the addControl parameter type (int vs.
unsigned) and adjusting the type if necessary.
- Around line 928-929: Remove the sentinel fake I2C device being appended with
address 255: stop creating the JsonObject via
newState["i2cBus"].as<JsonArray>().add<JsonObject>() and assigning
i2cDevice["address"] = 255 so the i2cBus array only contains real scan results;
if a non-empty-array indicator is needed, handle that in the UI layer instead of
adding a sentinel entry (look for usages of i2cDevice/newState["i2cBus"] in
ModuleIO.h and related scan/serialize functions to ensure no other code depends
on the sentinel).
- Around line 819-826: readPins() can call Wire.begin() repeatedly without
cleanup causing "Bus already started in Master Mode" on ESP32; before calling
Wire.begin(pinI2CSDA, pinI2CSCL) ensure you call Wire.end() if Wire is already
initialized (check Wire.isEnabled() or track a local/field flag), then call
Wire.begin(...) and log via EXT_LOGI/EXT_LOGE as before; also re-enable the
stabilization delay and explicit clock set (uncomment the delay(200) and
Wire.setClock(...) lines and set the clock to the recommended value for
Arduino-ESP32 3.x) so initI2C sequence (pins pinI2CSDA/pinI2CSCL, Wire.begin/
Wire.end, delay, Wire.setClock) is safe when readPins() runs repeatedly.
- Around line 905-908: Loop1s() is calling updateDevices() every second which
triggers a full I2C bus scan and causes blocking/traffic; change this so
updateDevices() is not invoked from loop1s(); instead call updateDevices() once
after a successful Wire.begin() (e.g., in readPins() after initializing I2C when
pinI2CSCL and pinI2CSDA are valid) and cache the discovered devices, and provide
an explicit on-demand way to rescan if needed; update references to pinI2CSCL
and pinI2CSDA checks so the scan only happens once after initialization and
remove the per-second call in loop1s().
🧹 Nitpick comments (4)
src/MoonLight/Nodes/Drivers/D_IMU.h (4)
22-27:gravityVector(Line 26) is declared but never used.The public
VectorFloat gravityVectormember is never read or written in this file. The privategravitymember (Line 139) is what's actually used for DMP calculations. Either removegravityVectoror wire it up to serve its intended purpose.
55-55: LocaldevStatuson Line 55 shadows the private member on Line 131.The
uint8_t devStatusdeclared insideonUpdateshadows the class member of the same name, leaving the member uninitialized and unused. Remove the member declaration on Line 131 since the variable is only needed locally.Proposed fix
// MPU control/status vars - uint8_t devStatus; // return status after each device operation (0 = success, !0 = error) uint8_t fifoBuffer[64]; // FIFO storage bufferAlso applies to: 131-131
100-105:gyrovalues also suffer float→int truncation (fractional degrees lost).
ypr[i] * 180 / M_PIproduces floats (e.g., 45.73°) that truncate to integers when assigned toCoord3D(int fields). This loses sub-degree resolution. The gravity scaling viaINT16_MAX(Lines 103–105) addresses the equivalent issue for gravity — consider a similar scaling approach for gyro, or document that integer-degree precision is intentional.
96-121: Redundant re-parsing of quaternion, accel, and gravity (Lines 111–113) with only a comment as justification.The comment "needed to repeat the following 3 lines (yes if you look at the output: otherwise not 0)" suggests an empirical workaround. The DMP packet is deterministic — re-parsing the same
fifoBuffershould yield the same results. This likely masks an issue with FIFO alignment or a stale-buffer read. Consider investigating the root cause rather than doubling the parsing work every 20ms cycle.
src/MoonBase/Modules/ModuleIO.h
Outdated
| addControl(rows, "DriveCap", "text", 0, 32, true); // ro | ||
| } | ||
|
|
||
| addControl(controls, "i2cFreq", "number", 0, 65534, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i2cFreq max value is too low for actual I2C clock frequencies.
Wire.getClock() returns values like 100000 (100 kHz) or 400000 (400 kHz), but the control max is 65534. Depending on how the UI enforces this bound, the displayed value will be clamped/truncated or cause unexpected behavior.
Proposed fix
- addControl(controls, "i2cFreq", "number", 0, 65534, true);
+ addControl(controls, "i2cFreq", "number", 0, 400000, true);Note: If addControl max parameter is limited to int range this should still work. If it needs to accommodate higher values (e.g., 1 MHz fast-mode plus), adjust accordingly.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| addControl(controls, "i2cFreq", "number", 0, 65534, true); | |
| addControl(controls, "i2cFreq", "number", 0, 400000, true); |
🤖 Prompt for AI Agents
In `@src/MoonBase/Modules/ModuleIO.h` at line 222, The i2cFreq control created via
addControl(controls, "i2cFreq", ...) uses a max of 65534 which is below typical
Wire.getClock() values (100000, 400000); update the max bound on the "i2cFreq"
control in ModuleIO.h to a larger value (e.g., 1000000 or UINT32_MAX) so real
I2C clock frequencies aren't clamped, ensuring the chosen max fits the
addControl parameter type (int vs. unsigned) and adjusting the type if
necessary.
| JsonObject i2cDevice = newState["i2cBus"].as<JsonArray>().add<JsonObject>(); | ||
| i2cDevice["address"] = 255; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sentinel entry with address 255 pollutes the device list.
A fake device at address 255 is appended after the real scan results. This is not a valid I2C address (7-bit range is 0–127) and will appear as a spurious device in the UI. If this is meant to ensure the i2cBus array is non-empty or signal end-of-list, consider handling that in the UI layer instead.
Proposed fix: remove the sentinel
EXT_LOGI(ML_TAG, "Found %d device(s)", count);
- JsonObject i2cDevice = newState["i2cBus"].as<JsonArray>().add<JsonObject>();
- i2cDevice["address"] = 255;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| JsonObject i2cDevice = newState["i2cBus"].as<JsonArray>().add<JsonObject>(); | |
| i2cDevice["address"] = 255; | |
| // Lines 928-929 removed - sentinel entry deleted |
🤖 Prompt for AI Agents
In `@src/MoonBase/Modules/ModuleIO.h` around lines 928 - 929, Remove the sentinel
fake I2C device being appended with address 255: stop creating the JsonObject
via newState["i2cBus"].as<JsonArray>().add<JsonObject>() and assigning
i2cDevice["address"] = 255 so the i2cBus array only contains real scan results;
if a non-empty-array indicator is needed, handle that in the UI layer instead of
adding a sentinel entry (look for usages of i2cDevice/newState["i2cBus"] in
ModuleIO.h and related scan/serialize functions to ensure no other code depends
on the sentinel).
Module IO - setup, onUpdate: i2cFreq is modifyable (in kHz) - setBoardPresetDefaults: i2c default 21,22 on esp32d0 and 8, 9 on others - wip - readPins: reinit Wire after i2c pins changed, using freq, and updateDevices(not every s) IMU module - remove gravityVector
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/MoonBase/Modules/ModuleIO.h`:
- Around line 813-836: Reset pinI2CSDA and pinI2CSCL to UINT8_MAX before
scanning the _state.data["pins"] array so stale GPIO values don't persist when a
preset has no I2C pins; in the function/block that iterates over pins (the loop
referencing pinObject["usage"] and setting pinI2CSDA/pinI2CSCL), explicitly set
pinI2CSDA = UINT8_MAX and pinI2CSCL = UINT8_MAX immediately prior to the for
loop, then keep the existing conditional Wire.begin(...) logic that checks for
UINT8_MAX.
In `@src/MoonLight/Nodes/Drivers/D_IMU.h`:
- Around line 40-80: Replace the hardcoded i2cInited = true in onUpdate with a
real I2C readiness check: query the ModuleIO state (e.g. moduleIO->pinI2CSDA
and/or moduleIO->pinI2CSCL != UINT8_MAX or a Wire.begin() success flag) to
determine if I2C is initialized before proceeding with MPU6050 initialization;
update the i2cInited variable accordingly and short-circuit the MPU setup (and
log a warning) when the check fails (references: onUpdate, i2cInited,
moduleIO->pinI2CSDA, Wire.begin).
- Around line 90-117: The code calls mpu.dmpGetLinearAccel(&aaReal, &aa,
&gravity) using aa which is never populated; restore or add a call to
mpu.dmpGetAccel(&aa, fifoBuffer) after obtaining the FIFO packet/quaternion
(e.g., after mpu.dmpGetQuaternion(&q, fifoBuffer)) so aa is filled before
dmpGetLinearAccel, and remove/adjust the commented duplicate lines accordingly
to ensure accell.x/y/z come from a valid aaReal.
🧹 Nitpick comments (1)
src/MoonBase/Modules/ModuleIO.h (1)
839-840:pinI2CSDA/pinI2CSCLare in the public section — consider moving toprivate.These members are declared between the public
readPins()/updateDevices()methods and the explicitprivate:label on line 943. If external access from the IMU driver is needed (e.g., to verify pin config), keep them public intentionally; otherwise move them below line 943.
|
@coderabbitai,
Also make a recommendation how to do the same with SPI (generic in Module IO, other modules can use it). |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 61352 🌐 Web query: 💡 Result: On ESP32-family SoCs there aren’t “fixed” I2C pins in hardware—the I2C signals are routed through the GPIO matrix, so you can map SDA/SCL to many usable GPIOs (subject to each pin’s constraints). [7] What people call “default I2C pins” usually means the defaults chosen by a software platform/board variant (most commonly Arduino-ESP32 Arduino-ESP32 (
|
| SoC | Default SDA | Default SCL | Notes / source |
|---|---|---|---|
| ESP32 | GPIO21 | GPIO22 | Arduino-ESP32 docs (Generic ESP32). [1] |
| ESP32-S2 | GPIO8 | GPIO9 | Mentioned as coming from pins_arduino.h for ESP32-S2. [2] |
| ESP32-S3 | GPIO8 | GPIO9 | Shown from variants/esp32s3/pins_arduino.h. [3] |
| ESP32-C3 | GPIO8 | GPIO9 | Stated as default for “ESP32C3 Dev Module” in Arduino-ESP32. [4] |
| ESP32-C6 | GPIO23 | GPIO22 | Shown as variants/esp32c6/pins_arduino.h defaults; also notes LP I2C pins SDA1=GPIO6, SCL1=GPIO7. [5] |
| ESP32-P4 | (no single universal default) | (no single universal default) | Boards commonly pick something like SDA=GPIO7 / SCL=GPIO8 (example ESP32-P4 boards/docs). [6] |
Reminder (all chips above)
If you don’t want the defaults, set pins explicitly (Arduino: Wire.begin(sda, scl) / Wire.setPins(sda, scl); ESP-IDF: configure sda_io_num / scl_io_num). ESP-IDF’s I2C setup is explicitly pin-config driven. [8]
🌐 Web query:
BMI160 Arduino ESP32 library IMU accelerometer gyroscope
💡 Result:
Good Arduino/ESP32 libraries for the Bosch BMI160 (accel + gyro)
- hanyazou / BMI160-Arduino (API header:
BMI160Gen.h)
- Supports SPI and I2C; example shows
BMI160.begin(...SPI_MODE...)and...I2C_MODE, 0x68)and basicreadGyro()usage. [1] - I2C note: tie SDO/SA0 to GND for the default address (example uses 0x68). [1]
- DFRobot_BMI160 (easy I2C-focused library; includes step counter helpers)
- Provides
I2cInit()andgetSensorData()methods; documents 0x68 when SDIO is GND and 0x69 as the alternate address. [2] - README lists FireBeetle-ESP32 as “Work Well” (so it’s known to run on ESP32-class boards). [2]
- EmotiBit_BMI160 (Arduino Library Manager listing exists)
- Published as “EmotiBit BMI160”; supports BMI160 (+ BMM150) and is based on the CurieIMU-style API. [3]
Minimal wiring (I2C, typical ESP32)
- VCC → 3.3V, GND → GND, SDA/SCL → ESP32 SDA/SCL
- Set address with SDO/SA0: low → 0x68, high → 0x69 (per DFRobot docs). [2]
Minimal code (hanyazou / BMI160Gen.h, I2C)
`#include` <Wire.h>
`#include` <BMI160Gen.h>
const int i2c_addr = 0x68;
void setup() {
Serial.begin(115200);
Wire.begin(); // (On ESP32 you can also pass SDA,SCL pins if needed)
BMI160.begin(BMI160GenClass::I2C_MODE, i2c_addr); // shown in repo example [1]
}
void loop() {
int gx, gy, gz;
BMI160.readGyro(gx, gy, gz); // shown in repo example [1]
Serial.printf("g: %d %d %d\n", gx, gy, gz);
delay(200);
}Sources: [1] [2] [3]
🌐 Web query:
Arduino unified IMU library MPU6050 BMI160 LSM6DS3 multi-sensor support
💡 Result:
- Closest thing to a “unified” Arduino IMU API: Adafruit’s Unified Sensor abstraction (
Adafruit_Sensor) which standardizes reads intosensors_event_tacross many Adafruit drivers. (arduinolibraries.info) - MPU6050:
Adafruit_MPU6050uses Adafruit’s stack (BusIO + Unified Sensor) and exposes accel/gyro (and temp). (learn.adafruit.com) - LSM6DS3 family: Adafruit’s
Adafruit_LSM6DSlibrary supports multiple LSM6DS variants (including LSM6DS33 and others) and also uses Unified Sensor. (learn.adafruit.com) - BMI160: There isn’t a commonly-used Arduino library for BMI160 that plugs into Adafruit Unified Sensor the same way; typical options are vendor/community drivers like DFRobot_BMI160 or EmotiBit_BMI160, with their own APIs. (arduinolibraries.info)
Practical multi-sensor approach (MPU6050 + LSM6DS3 + BMI160):
- Use Adafruit Unified Sensor for MPU6050 + LSM6DS3*, so your code reads both via a similar
getEvent()pattern. (learn.adafruit.com) - Wrap your BMI160 driver in a small adapter that outputs the same fields (accel/gyro/temp) in a struct compatible with your unified path (or mimic Adafruit’s
sensors_event_t). (arduinolibraries.info)
If you tell me which BMI160 breakout you’re using (DFRobot? Bosch? other) and whether you need I2C or SPI, I can point you to the best-maintained driver + sketch an adapter interface.
Citations:
- 1: https://www.arduinolibraries.info/libraries/adafruit-unified-sensor?utm_source=openai
- 2: https://learn.adafruit.com/mpu6050-6-dof-accelerometer-and-gyro/arduino?utm_source=openai
- 3: https://learn.adafruit.com/lsm6dsox-and-ism330dhc-6-dof-imu/arduino?utm_source=openai
- 4: https://www.arduinolibraries.info/libraries/df-robot_bmi160?utm_source=openai
- 5: https://learn.adafruit.com/mpu6050-6-dof-accelerometer-and-gyro/arduino?utm_source=openai
- 6: https://www.arduinolibraries.info/libraries/df-robot_bmi160?utm_source=openai
@ewowi, I've completed a thorough analysis of the I2C centralization approach, researched ESP32 pin defaults, and explored BMI160 and generic IMU library options. Here's my comprehensive review:
✅ Architectural Review: I2C Centralization in ModuleIO.h
Is the centralized approach correct?
Yes, this is an excellent architectural decision! The centralization of I2C in ModuleIO.h provides:
- ✅ Single Source of Truth: One place manages Wire initialization, pin configuration, and bus state
- ✅ Prevents conflicts: Avoids multiple drivers calling Wire.begin() with different pins
- ✅ Runtime reconfiguration: Pin changes trigger automatic Wire reinitialization
- ✅ Bus discovery: Centralized
updateDevices()scans all I2C devices once - ✅ Frequency control: Single i2cFreq parameter affects all I2C devices
Current Implementation Quality
ModuleIO.h I2C Management:
- ✅ Pin reading from UI configuration (pin_I2C_SDA, pin_I2C_SCL)
- ✅ Wire.end() before reinit to clean up previous state
- ✅ Frequency configuration with Wire.setClock()
- ✅ Device scanning with updateDevices()
⚠️ Issue: i2cBus only shows addresses, not device names
D_IMU.h Integration:
- ✅ Removed I2C initialization logic (no longer calls Wire.begin)
- ✅ Relies on Module IO for bus setup
⚠️ Issue:bool i2cInited = true; // todo: check in moduleIO if successfull- hardcoded, should actually check ModuleIO state
📌 Recommendation 1: Default I2C Pins for All ESP32 Variants
Based on Arduino-ESP32 platform defaults, update the default pin configuration in ModuleIO.h:
// In setBoardPresetDefaults() for board_none (default case):
`#ifdef` CONFIG_IDF_TARGET_ESP32
pinAssigner.assignPin(21, pin_I2C_SDA); // ESP32 classic
pinAssigner.assignPin(22, pin_I2C_SCL);
`#elif` defined(CONFIG_IDF_TARGET_ESP32S2)
pinAssigner.assignPin(8, pin_I2C_SDA); // ESP32-S2
pinAssigner.assignPin(9, pin_I2C_SCL);
`#elif` defined(CONFIG_IDF_TARGET_ESP32S3)
pinAssigner.assignPin(8, pin_I2C_SDA); // ESP32-S3
pinAssigner.assignPin(9, pin_I2C_SCL);
`#elif` defined(CONFIG_IDF_TARGET_ESP32C3)
pinAssigner.assignPin(8, pin_I2C_SDA); // ESP32-C3
pinAssigner.assignPin(9, pin_I2C_SCL);
`#elif` defined(CONFIG_IDF_TARGET_ESP32C6)
pinAssigner.assignPin(23, pin_I2C_SDA); // ESP32-C6
pinAssigner.assignPin(22, pin_I2C_SCL);
`#elif` defined(CONFIG_IDF_TARGET_ESP32P4)
pinAssigner.assignPin(7, pin_I2C_SDA); // ESP32-P4 (common board default)
pinAssigner.assignPin(8, pin_I2C_SCL);
`#else`
pinAssigner.assignPin(21, pin_I2C_SDA); // Fallback
pinAssigner.assignPin(22, pin_I2C_SCL);
`#endif`Note: Your current code already handles ESP32 vs others, but could be more explicit for C6 and P4.
📌 Recommendation 2: Device Naming Strategy for i2cBus
Problem: i2cBus control only shows numeric addresses (e.g., 0x68), not device names.
Solution: Implement a device identification registry
Step 1: Add device name field to i2cBus control
In ModuleIO.h:
control = addControl(controls, "i2cBus", "rows");
control["crud"] = "r";
rows = control["n"].to<JsonArray>();
{
addControl(rows, "address", "number", 0, 255, true); // ro
addControl(rows, "name", "text", 0, 32, true); // ro - NEW
}Step 2: Create I2C device registry in ModuleIO
// In ModuleIO.h private section:
struct I2CDeviceInfo {
uint8_t address;
const char* name;
Node* owner; // Which node claimed this device
};
std::vector<I2CDeviceInfo> i2cDeviceRegistry;
public:
// Allow drivers to register their I2C devices
void registerI2CDevice(uint8_t address, const char* name, Node* owner) {
for (auto& dev : i2cDeviceRegistry) {
if (dev.address == address) {
dev.name = name;
dev.owner = owner;
return;
}
}
i2cDeviceRegistry.push_back({address, name, owner});
}Step 3: Update updateDevices() to use registry
void updateDevices() {
JsonDocument doc;
doc["i2cBus"].to<JsonArray>();
JsonObject newState = doc.as<JsonObject>();
EXT_LOGI(ML_TAG, "Scanning I2C bus...");
byte count = 0;
for (byte i = 1; i < 127; i++) {
Wire.beginTransmission(i);
if (Wire.endTransmission() == 0) {
JsonObject i2cDevice = newState["i2cBus"].as<JsonArray>().add<JsonObject>();
i2cDevice["address"] = i;
// Look up device name in registry
const char* deviceName = "Unknown";
for (const auto& dev : i2cDeviceRegistry) {
if (dev.address == i) {
deviceName = dev.name;
break;
}
}
i2cDevice["name"] = deviceName;
EXT_LOGI(ML_TAG, "Found I2C device at address 0x%02X: %s", i, deviceName);
count++;
}
}
EXT_LOGI(ML_TAG, "Found %d device(s)", count);
doc["i2cFreq"] = Wire.getClock() / 1000;
update(newState, ModuleState::update, _moduleName);
}Step 4: IMU driver registers itself
In D_IMU.h onUpdate():
if (control["on"] == true) {
bool i2cInited = true; // TODO: get from ModuleIO
if (i2cInited) {
if (board == 0) { // MPU6050
// Register this device with Module IO
ModuleIO* io = modules->getModuleOfClass<ModuleIO>();
if (io) {
io->registerI2CDevice(0x68, "MPU6050", this); // or 0x69 if AD0 high
}
mpu.initialize();
// ... rest of code
}
}
}Alternative Simpler Approach: Known I2C address database in ModuleIO:
const char* getDeviceNameByAddress(uint8_t addr) {
switch(addr) {
case 0x68: return "MPU6050/MPU9250"; // Could be either
case 0x69: return "MPU6050/MPU9250 (AD0=HIGH)";
case 0x76: return "BME280/BMP280";
case 0x77: return "BME280/BMP280 (ALT)";
case 0x3C: return "OLED SSD1306";
case 0x3D: return "OLED SSD1306 (ALT)";
case 0x40: return "PCA9685/INA219";
// ... add more as needed
default: return "Unknown";
}
}This is simpler but less accurate (address collisions between different chips). I recommend the registry approach for accuracy.
📌 Recommendation 3: BMI160 IMU Support
Library Recommendation
Use hanyazou/BMI160-Arduino (BMI160Gen.h API) - supports both I2C and SPI with simple API.
Why this library:
- ✅ I2C and SPI support
- ✅ I2C addresses: 0x68 (SDO=GND) or 0x69 (SDO=VCC)
- ✅ Similar API structure to MPU6050 libraries
- ✅ Actively maintained
Add to platformio.ini:
lib_deps =
ElectronicCats/MPU6050 @ 1.3.0 ; for MPU6050
hanyazou/BMI160-Arduino @ ^1.0.0 ; for BMI160Implementation in D_IMU.h
`#if` FT_MOONLIGHT
`#include` <MPU6050_6Axis_MotionApps20.h>
`#include` <BMI160Gen.h> // NEW
class IMUDriver : public Node {
// ... existing code ...
void onUpdate(const Char<20>& oldValue, const JsonObject& control) override {
if (!control["on"].isNull()) {
if (control["on"] == true) {
bool i2cInited = true; // TODO: check ModuleIO
if (i2cInited) {
if (board == 0) { // MPU6050
// Register with Module IO
ModuleIO* io = modules->getModuleOfClass<ModuleIO>();
if (io) io->registerI2CDevice(0x68, "MPU6050", this);
mpu.initialize();
// ... existing MPU6050 code ...
} else if (board == 1) { // BMI160 - NEW
// Register with Module IO
ModuleIO* io = modules->getModuleOfClass<ModuleIO>();
if (io) io->registerI2CDevice(0x68, "BMI160", this);
BMI160.begin(BMI160GenClass::I2C_MODE, 0x68);
if (BMI160.getDeviceID() == 0xD1) { // BMI160 device ID
EXT_LOGI(ML_TAG, "BMI160 connection successful");
motionTrackingReady = true;
} else {
EXT_LOGW(ML_TAG, "BMI160 connection failed");
}
}
}
}
}
}
void loop20ms() override {
if (!motionTrackingReady) return;
if (board == 0) { // MPU6050
// ... existing MPU6050 loop code ...
} else if (board == 1) { // BMI160 - NEW
int gx, gy, gz, ax, ay, az;
BMI160.readGyro(gx, gy, gz);
BMI160.readAccelerometer(ax, ay, az);
// Convert raw values to degrees (BMI160 gyro: 16.4 LSB/°/s at ±2000°/s range)
gyro.x = gx / 16.4f;
gyro.y = gy / 16.4f;
gyro.z = gz / 16.4f;
// Convert raw accel values (BMI160 accel: 16384 LSB/g at ±2g range)
accell.x = ax;
accell.y = ay;
accell.z = az;
// Calculate gravity vector from accelerometer
float norm = sqrt(ax*ax + ay*ay + az*az);
if (norm > 0) {
sharedData.gravity.x = (ax / norm) * INT16_MAX;
sharedData.gravity.y = (ay / norm) * INT16_MAX;
sharedData.gravity.z = (az / norm) * INT16_MAX;
}
}
}
private:
MPU6050 mpu;
// BMI160 is accessed via static BMI160 object from library
};
`#endif`Note: BMI160 uses I2C address 0x68 when SDO is grounded, same as MPU6050, so they can't coexist on the same bus without address changes.
📌 Recommendation 4: Generic IMU Library Strategy
Reality Check
Adafruit Unified Sensor is the closest to a unified Arduino IMU API, but BMI160 doesn't have Adafruit Unified Sensor support.
Recommended Approach: Internal Abstraction Layer
Don't try to use a single external library - instead, create your own thin abstraction:
// In D_IMU.h - add this interface
struct IMUSensorData {
Coord3D gyro; // degrees/sec
Coord3D accell; // raw or m/s²
Coord3D gravity; // normalized
bool valid;
};
class IMUDriver : public Node {
// ... existing code ...
private:
// Unified read method
IMUSensorData readSensor() {
IMUSensorData data = {};
if (board == 0) { // MPU6050
if (mpu.dmpGetCurrentFIFOPacket(fifoBuffer)) {
mpu.dmpGetQuaternion(&q, fifoBuffer);
mpu.dmpGetGravity(&gravity, &q);
mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
data.gyro.x = ypr[1] * 180 / M_PI;
data.gyro.y = ypr[0] * 180 / M_PI;
data.gyro.z = ypr[2] * 180 / M_PI;
mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity);
data.accell.x = aaReal.x;
data.accell.y = aaReal.y;
data.accell.z = aaReal.z;
data.gravity.x = gravity.x * INT16_MAX;
data.gravity.y = gravity.y * INT16_MAX;
data.gravity.z = gravity.z * INT16_MAX;
data.valid = true;
}
} else if (board == 1) { // BMI160
int gx, gy, gz, ax, ay, az;
BMI160.readGyro(gx, gy, gz);
BMI160.readAccelerometer(ax, ay, az);
data.gyro.x = gx / 16.4f;
data.gyro.y = gy / 16.4f;
data.gyro.z = gz / 16.4f;
data.accell.x = ax;
data.accell.y = ay;
data.accell.z = az;
float norm = sqrt(ax*ax + ay*ay + az*az);
if (norm > 0) {
data.gravity.x = (ax / norm) * INT16_MAX;
data.gravity.y = (ay / norm) * INT16_MAX;
data.gravity.z = (az / norm) * INT16_MAX;
}
data.valid = true;
}
return data;
}
public:
void loop20ms() override {
if (!motionTrackingReady) return;
IMUSensorData data = readSensor();
if (data.valid) {
gyro = data.gyro;
accell = data.accell;
sharedData.gravity = data.gravity;
EXT_LOGD(ML_TAG, "Gyro: %f %f %f", gyro.x, gyro.y, gyro.z);
}
}
};Benefits:
- ✅ Single loop20ms() code path
- ✅ Easy to add new IMU chips (just add another
else ifin readSensor()) - ✅ No dependency on unified external library
- ✅ Keep using best-in-class chip-specific libraries
Future IMUs to add:
- LSM6DS3 / LSM6DSO (common on newer boards)
- ICM-20948 (9-axis with magnetometer)
- BNO055/BNO085 (fusion-ready IMUs)
📌 Recommendation 5: SPI Centralization (Similar to I2C)
Current State
- ✅ SPI pin types defined in ModuleIO (pin_SPI_SCK, pin_SPI_MISO, pin_SPI_MOSI)
⚠️ SPI.begin() called inEthernetSettingsService.cpp(not centralized)
Proposed Changes
1. Add SPI management to ModuleIO.h:
// In setupDefinition():
addControl(controls, "spiFreq", "number", 0, 80000, false, "kHz");
control = addControl(controls, "spiBus", "rows");
control["crud"] = "r";
rows = control["n"].to<JsonArray>();
{
addControl(rows, "cs", "number", 0, SOC_GPIO_PIN_COUNT-1, true); // ro
addControl(rows, "name", "text", 0, 32, true); // ro
}
// Private members:
uint8_t pinSPISCK = UINT8_MAX;
uint8_t pinSPIMISO = UINT8_MAX;
uint8_t pinSPIMOSI = UINT8_MAX;
std::vector<SPIDeviceInfo> spiDeviceRegistry;
struct SPIDeviceInfo {
uint8_t csPin;
const char* name;
Node* owner;
};
// Public methods:
void registerSPIDevice(uint8_t csPin, const char* name, Node* owner);
bool isSPIInitialized() const { return pinSPISCK != UINT8_MAX && pinSPIMISO != UINT8_MAX && pinSPIMOSI != UINT8_MAX; }2. Initialize SPI in readPins():
void readPins() {
// ... existing I2C code ...
// SPI pin detection
for (JsonObject pinObject : _state.data["pins"].as<JsonArray>()) {
uint8_t usage = pinObject["usage"];
if (usage == pin_SPI_SCK) {
pinSPISCK = pinObject["GPIO"];
EXT_LOGD(ML_TAG, "SPISCK found %d", pinSPISCK);
}
if (usage == pin_SPI_MISO) {
pinSPIMISO = pinObject["GPIO"];
EXT_LOGD(ML_TAG, "SPIMISO found %d", pinSPIMISO);
}
if (usage == pin_SPI_MOSI) {
pinSPIMOSI = pinObject["GPIO"];
EXT_LOGD(ML_TAG, "SPIMOSI found %d", pinSPIMOSI);
}
}
if (pinSPISCK != UINT8_MAX && pinSPIMISO != UINT8_MAX && pinSPIMOSI != UINT8_MAX) {
SPI.end(); // Clean up any previous SPI initialization
SPI.begin(pinSPISCK, pinSPIMISO, pinSPIMOSI);
uint16_t frequency = _state.data["spiFreq"];
if (frequency > 0) {
// SPI frequency is set per-transaction, not globally like I2C
EXT_LOGI(ML_TAG, "SPI bus initialized: sck:%d miso:%d mosi:%d", pinSPISCK, pinSPIMISO, pinSPIMOSI);
}
}
}3. Update EthernetSettingsService to use ModuleIO's SPI:
// In EthernetSettingsService.cpp, replace SPI.begin() with:
ModuleIO* io = /* get ModuleIO reference */;
if (io && io->isSPIInitialized()) {
// SPI already initialized by ModuleIO, just use it
ETH.begin(v_ETH_PHY_TYPE, v_ETH_PHY_ADDR, v_ETH_PHY_CS, v_ETH_PHY_IRQ, v_ETH_PHY_RST, SPI);
} else {
EXT_LOGW("Ethernet", "SPI not initialized by ModuleIO");
}Benefits:
- ✅ Consistent architecture (I2C and SPI both in ModuleIO)
- ✅ Single source of truth for bus configuration
- ✅ Easy to add SPI-based drivers (displays, SD cards, etc.)
- ✅ Prevents pin conflicts
📋 Summary of Follow-up Actions
High Priority:
- ✅ Fix i2cInited check in D_IMU.h - currently hardcoded to
true - ✅ Add device naming to i2cBus - implement registry approach
- ✅ Update ESP32-C6 and ESP32-P4 default pins - explicit per-variant defaults
- ✅ Add BMI160 support - use hanyazou/BMI160-Arduino library
Medium Priority:
- ✅ Create internal IMU abstraction - unified readSensor() method
- ✅ Centralize SPI in ModuleIO - mirror I2C architecture
- ✅ Document I2C/SPI device registration - for future driver developers
Low Priority:
- Add more IMU chips (LSM6DS3, ICM-20948, BNO055)
- Add SPI device scanning (enumerate CS pins with known devices)
- Add I2C pull-up resistor strength configuration
Would you like me to generate code for any specific recommendation, or shall I create a GitHub issue with this analysis as a follow-up task?
🧠 Learnings used
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-26T14:59:22.091Z
Learning: In MoonModules/MoonLight PR `#114`: ModuleDevices deadlock caused by: (1) StatefulService.h line 204-205 bug - after 500ms mutex timeout, continues WITHOUT acquiring mutex, causing race conditions and JSON corruption. (2) receiveUDP() while-loop processes own UDP broadcasts via loopback, creating circular call chain: sendUDP→broadcast→receiveUDP→updateDevices→update→handlers→sendUDP. (3) updateDevices() holds _accessMutex during expensive sort/JSON operations with 15 devices, can exceed 500ms with nested calls. Solution: Fix StatefulService to wait forever or reboot on timeout; skip own broadcasts by checking message.name == ownHostname; optimize updateDevices to sort outside mutex.
|
@coderabbitai, I also want to add my-sr602 pir and digital microphones like the ics43432/4 or inmp441 How to add these, generic solution like for i2c and spi needed ? And do i2s microphones interfere with i2s FastLED drivers ? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 114151 🌐 Web query: 💡 Result: For INMP441 and ICS-43432/ICS43432 on ESP32 + Arduino, you typically don’t need a mic-specific library—just the Arduino-ESP32 I2S API (or the underlying ESP-IDF I2S driver). (docs.espressif.com) Recommended (current) option: Arduino-ESP32
|
| Concern | FastLED RMT + I2S Mic | FastLED Old I2S LCD + I2S Mic |
|---|---|---|
| Direct peripheral conflict | ✅ No conflict | ❌ Conflict! Don't use! |
| DMA bandwidth competition | ||
| Recommended? | ✅ Safe combination | ❌ Avoid - use RMT instead |
📋 Action Items Summary
For PIR Sensors:
- ✅ Add
pin_PIRusage type to ModuleIO.h - ✅ Create
D_PIR.hdriver (simple GPIO input reading) - ✅ Register in ModuleDrivers.h
For I2S Microphones:
- ✅ Centralize I2S bus management in ModuleIO.h (similar to I2C/SPI)
- ✅ Initialize I2S bus when pins are assigned
- ✅ Create
D_Microphone.hdriver - ✅ Add audio level to SharedData for effects to use
- ✅ Consider adding FFT analysis if you want frequency-reactive effects
Interference Mitigation:
- ✅ Verify you're using FastLED RMT mode (not I2S LCD mode)
- ✅ Allocate DMA buffers in internal RAM
- ✅ Minimize flash/WiFi activity during LED updates
- ✅ Consider dual-core task pinning for heavy loads
Would you like me to generate the complete implementation code for either the PIR driver or the I2S microphone driver?
🧠 Learnings used
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-26T14:59:22.091Z
Learning: In MoonModules/MoonLight PR `#114`: ModuleDevices deadlock caused by: (1) StatefulService.h line 204-205 bug - after 500ms mutex timeout, continues WITHOUT acquiring mutex, causing race conditions and JSON corruption. (2) receiveUDP() while-loop processes own UDP broadcasts via loopback, creating circular call chain: sendUDP→broadcast→receiveUDP→updateDevices→update→handlers→sendUDP. (3) updateDevices() holds _accessMutex during expensive sort/JSON operations with 15 devices, can exceed 500ms with nested calls. Solution: Fix StatefulService to wait forever or reboot on timeout; skip own broadcasts by checking message.name == ownHostname; optimize updateDevices to sort outside mutex.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-02-05T13:20:45.988Z
Learning: In MoonLight with FastLED 3.11 on ESP-IDF 5.x+ (Arduino core 3.x): FastLED defaults to RMT5 driver (wraps Espressif's led_strip component), which has known ISR handler lifecycle issues causing repeated "Handler detached" messages every ~500ms and severe performance degradation (e.g., 2 FPS). RMT5 is more prone to Wi-Fi/interrupt jitter than legacy RMT4. Solution: force legacy RMT4 by adding `-D FASTLED_RMT5=0` to build flags in platformio.ini, or switch to FastLED's parallel I/O drivers which don't use RMT. RMT4 and RMT5 cannot coexist in the same binary.
ModuleIO: add I2C pins for default boards per MCU type, reset pins before update IMU Driver: fix accell calculation, BMI160 code (commented ATM)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@src/MoonBase/Modules/ModuleIO.h`:
- Around line 578-601: Remove the redundant initial I2C pin assignment block
that calls pinAssigner.assignPin(21, pin_I2C_SDA)/assignPin(22, pin_I2C_SCL)
(the block guarded by `#ifdef` CONFIG_IDF_TARGET_ESP32 at the top) because
setBoardPresetDefaults() already performs target-specific assignments; keep the
later granular conditional block (the `#ifdef` / `#elif` chain that includes
CONFIG_IDF_TARGET_ESP32, ESP32S2/S3/C3, ESP32C6, ESP32P4, and the fallback) and
delete the earlier duplicate assignments so pinAssigner.assignPin is only
invoked once per board preset.
- Around line 848-859: The i2cFreq control currently defaults to 0 and leads to
calling Wire.begin(pinI2CSDA, pinI2CSCL, 0) and showing "0 kHz" in the UI;
update the setupDefinition to provide a sensible default (e.g., 100) for i2cFreq
and add a guard in the code that reads _state.data["i2cFreq"] (the
readPins/initI2C area) to treat 0 as the default value (replace with 100 or
another chosen default) before multiplying by 1000 and passing to
Wire.begin(pinI2CSDA, pinI2CSCL, frequency * 1000); ensure any logged frequency
uses the adjusted value so the UI shows a nonzero kHz and maintain behavior that
falls back to a safe clock when frequency is absent or zero.
In `@src/MoonLight/Nodes/Drivers/D_IMU.h`:
- Around line 29-35: The board selection control (added in setup via
addControl(board,...)) can flip at runtime without clearing state, so update
handling must reset motion-tracking state; add a guard in onUpdate (or a change
callback for the "board" control) that detects when board changes and sets
motionTrackingReady = false and disables/re-initializes motion tracking (e.g.,
set isEnabled = false or call the MPU6050 teardown/init sequence) so loop20ms's
BMI160 branch cannot run while MPU6050 state remains valid; reference
functions/vars: setup, addControl(board,...), board, onUpdate, loop20ms,
motionTrackingReady, isEnabled (or the MPU init/teardown helpers) to locate
where to implement this reset.
- Around line 38-88: Add a cleanup path for when the driver is turned off by
modifying onUpdate in D_IMU: when control["on"] == false (or control["on"] is
present and false) call the MPU shutdown steps—disable the DMP via
mpu.setDMPEnabled(false) (or equivalent), clear any interrupt/status flags, stop
any periodic reads, and set motionTrackingReady = false; also ensure any
board==1 BMI160 cleanup is mirrored. Then implement the same teardown in the
D_IMU destructor (~D_IMU) to disable DMP/interrupts and reset
motionTrackingReady so loop20ms stops reading after node removal. Ensure you
reference the existing symbols onUpdate, motionTrackingReady, mpu.setDMPEnabled
(and ~D_IMU) when making changes.
🧹 Nitpick comments (2)
src/MoonLight/Nodes/Drivers/D_IMU.h (1)
109-118: Stale comments around the accelerometer read — clean up for clarity.
- Line 109: "display real acceleration, adjusted to remove gravity" is an orphaned comment (no display code follows it).
- Line 113: "needed to repeat the following 3 lines" is misleading since only
dmpGetAccel(line 115) is active; the other two remain commented out.Consider removing these leftover comments to avoid confusing future readers about why certain calls are or aren't needed.
src/MoonBase/Modules/ModuleIO.h (1)
862-863: I2C pin members are public — verify this is intentional.
pinI2CSDAandpinI2CSCLare declared in thepublicsection, presumably soD_IMU.hand other drivers can query them. If external read access is the intent, this is fine; consider adding a brief comment to document the intended usage.
| #ifdef CONFIG_IDF_TARGET_ESP32 | ||
| pinAssigner.assignPin(21, pin_I2C_SDA); | ||
| pinAssigner.assignPin(22, pin_I2C_SCL); | ||
| #else | ||
| pinAssigner.assignPin(8, pin_I2C_SDA); | ||
| pinAssigner.assignPin(9, pin_I2C_SCL); | ||
| #endif | ||
| // In setBoardPresetDefaults() for board_none (default case): | ||
| #ifdef CONFIG_IDF_TARGET_ESP32 | ||
| pinAssigner.assignPin(21, pin_I2C_SDA); // ESP32 classic | ||
| pinAssigner.assignPin(22, pin_I2C_SCL); | ||
| #elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) | ||
| pinAssigner.assignPin(8, pin_I2C_SDA); // ESP32-C3 | ||
| pinAssigner.assignPin(9, pin_I2C_SCL); | ||
| #elif defined(CONFIG_IDF_TARGET_ESP32C6) | ||
| pinAssigner.assignPin(23, pin_I2C_SDA); // ESP32-C6 | ||
| pinAssigner.assignPin(22, pin_I2C_SCL); | ||
| #elif defined(CONFIG_IDF_TARGET_ESP32P4) | ||
| pinAssigner.assignPin(7, pin_I2C_SDA); // ESP32-P4 (common board default) | ||
| pinAssigner.assignPin(8, pin_I2C_SCL); | ||
| #else | ||
| pinAssigner.assignPin(21, pin_I2C_SDA); // Fallback | ||
| pinAssigner.assignPin(22, pin_I2C_SCL); | ||
| #endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate I2C pin assignments — first block is dead code.
Lines 578–584 assign I2C pins, then lines 586–601 immediately overwrite them with a more granular per-target mapping. The first block is redundant. For CONFIG_IDF_TARGET_ESP32, pins 21/22 are assigned twice; for other targets (e.g., C6), the first assignment (8/9) is silently overwritten.
Proposed fix — remove the first block
- `#ifdef` CONFIG_IDF_TARGET_ESP32
- pinAssigner.assignPin(21, pin_I2C_SDA);
- pinAssigner.assignPin(22, pin_I2C_SCL);
- `#else`
- pinAssigner.assignPin(8, pin_I2C_SDA);
- pinAssigner.assignPin(9, pin_I2C_SCL);
- `#endif`
- // In setBoardPresetDefaults() for board_none (default case):
`#ifdef` CONFIG_IDF_TARGET_ESP32
pinAssigner.assignPin(21, pin_I2C_SDA); // ESP32 classic
pinAssigner.assignPin(22, pin_I2C_SCL);🤖 Prompt for AI Agents
In `@src/MoonBase/Modules/ModuleIO.h` around lines 578 - 601, Remove the redundant
initial I2C pin assignment block that calls pinAssigner.assignPin(21,
pin_I2C_SDA)/assignPin(22, pin_I2C_SCL) (the block guarded by `#ifdef`
CONFIG_IDF_TARGET_ESP32 at the top) because setBoardPresetDefaults() already
performs target-specific assignments; keep the later granular conditional block
(the `#ifdef` / `#elif` chain that includes CONFIG_IDF_TARGET_ESP32, ESP32S2/S3/C3,
ESP32C6, ESP32P4, and the fallback) and delete the earlier duplicate assignments
so pinAssigner.assignPin is only invoked once per board preset.
| if (pinI2CSCL != UINT8_MAX && pinI2CSDA != UINT8_MAX) { | ||
| Wire.end(); // Clean up any previous I2C initialization | ||
| delay(100); | ||
| uint16_t frequency = _state.data["i2cFreq"]; | ||
| if (Wire.begin(pinI2CSDA, pinI2CSCL, frequency * 1000)) { | ||
| EXT_LOGI(ML_TAG, "initI2C Wire sda:%d scl:%d freq:%d kHz", pinI2CSDA, pinI2CSCL, frequency); | ||
| // delay(200); // Give I2C bus time to stabilize | ||
| // Wire.setClock(50000); // Explicitly set to 100kHz | ||
| updateDevices(); | ||
| } else | ||
| EXT_LOGE(ML_TAG, "initI2C Wire failed"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Zero i2cFreq default will pass 0 Hz to Wire.begin().
The i2cFreq control has no "default" set in setupDefinition (line 222), so it defaults to 0. On line 852, frequency * 1000 evaluates to 0, and Wire.begin(sda, scl, 0) is called. While Arduino-ESP32's Wire.begin may internally fall back to 100 kHz when given 0, this behavior is implicit and undocumented. A user seeing "0 kHz" in the UI is also confusing.
Proposed fix — add a default and guard against zero
In setupDefinition, add a default:
- addControl(controls, "i2cFreq", "number", 0, 65534, false, "kHz");
+ control = addControl(controls, "i2cFreq", "number", 10, 1000, false, "kHz");
+ control["default"] = 100; // 100 kHz standard modeAnd in readPins, guard:
uint16_t frequency = _state.data["i2cFreq"];
+ if (frequency == 0) frequency = 100; // fallback to 100 kHz
if (Wire.begin(pinI2CSDA, pinI2CSCL, frequency * 1000)) {🤖 Prompt for AI Agents
In `@src/MoonBase/Modules/ModuleIO.h` around lines 848 - 859, The i2cFreq control
currently defaults to 0 and leads to calling Wire.begin(pinI2CSDA, pinI2CSCL, 0)
and showing "0 kHz" in the UI; update the setupDefinition to provide a sensible
default (e.g., 100) for i2cFreq and add a guard in the code that reads
_state.data["i2cFreq"] (the readPins/initI2C area) to treat 0 as the default
value (replace with 100 or another chosen default) before multiplying by 1000
and passing to Wire.begin(pinI2CSDA, pinI2CSCL, frequency * 1000); ensure any
logged frequency uses the adjusted value so the UI shows a nonzero kHz and
maintain behavior that falls back to a safe clock when frequency is absent or
zero.
| void setup() override { | ||
| addControl(gyro, "gyro", "coord3D"); | ||
| addControl(accell, "accell", "coord3D"); | ||
| // isEnabled = false; // need to enable after fresh setup | ||
| addControl(board, "board", "select"); | ||
| addControlValue("MPU6050"); | ||
| addControlValue("BMI160"); // not supported yet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
board selection has no runtime guard — switching board type while running could cause issues.
If a user changes board from 0 (MPU6050) to 1 (BMI160) at runtime without re-triggering onUpdate, loop20ms will enter the BMI160 branch (which is a no-op) while motionTrackingReady is still true from the MPU6050 init. Consider resetting motionTrackingReady when board changes, or at minimum documenting that a re-enable is required.
🤖 Prompt for AI Agents
In `@src/MoonLight/Nodes/Drivers/D_IMU.h` around lines 29 - 35, The board
selection control (added in setup via addControl(board,...)) can flip at runtime
without clearing state, so update handling must reset motion-tracking state; add
a guard in onUpdate (or a change callback for the "board" control) that detects
when board changes and sets motionTrackingReady = false and
disables/re-initializes motion tracking (e.g., set isEnabled = false or call the
MPU6050 teardown/init sequence) so loop20ms's BMI160 branch cannot run while
MPU6050 state remains valid; reference functions/vars: setup,
addControl(board,...), board, onUpdate, loop20ms, motionTrackingReady, isEnabled
(or the MPU init/teardown helpers) to locate where to implement this reset.
| void onUpdate(const Char<20>& oldValue, const JsonObject& control) override { | ||
| // add your custom onUpdate code here | ||
| if (!control["on"].isNull()) { // control is the node n case of on! | ||
| if (control["on"] == true) { | ||
| bool i2cInited = true; // todo: check in moduleIO if successfull | ||
| if (i2cInited) { | ||
| if (board == 0) { // MPU6050 | ||
| mpu.initialize(); | ||
|
|
||
| // delay(100); | ||
|
|
||
| // verify connection | ||
| if (mpu.testConnection()) { | ||
| EXT_LOGI(ML_TAG, "MPU6050 connection successful Initializing DMP..."); | ||
| uint8_t devStatus = mpu.dmpInitialize(); | ||
|
|
||
| if (devStatus == 0) { | ||
| // // Calibration Time: generate offsets and calibrate our MPU6050 | ||
| mpu.CalibrateAccel(6); | ||
| mpu.CalibrateGyro(6); | ||
| // mpu.PrintActiveOffsets(); | ||
|
|
||
| mpu.setDMPEnabled(true); // mandatory | ||
|
|
||
| // mpuIntStatus = mpu.getIntStatus(); | ||
|
|
||
| motionTrackingReady = true; | ||
| } else { | ||
| // ERROR! | ||
| // 1 = initial memory load failed | ||
| // 2 = DMP configuration updates failed | ||
| // (if it's going to break, usually the code will be 1) | ||
| EXT_LOGW(ML_TAG, "DMP Initialization failed (code %d)", devStatus); | ||
| } | ||
| } else | ||
| EXT_LOGW(ML_TAG, "Testing device connections MPU6050 connection failed"); | ||
| } else if (board == 1) { // BMI160 - NEW | ||
|
|
||
| // BMI160.begin(BMI160GenClass::I2C_MODE, 0x68); | ||
|
|
||
| // if (BMI160.getDeviceID() == 0xD1) { // BMI160 device ID | ||
| // EXT_LOGI(ML_TAG, "BMI160 connection successful"); | ||
| // motionTrackingReady = true; | ||
| // } else { | ||
| // EXT_LOGW(ML_TAG, "BMI160 connection failed"); | ||
| // } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No cleanup path when the driver is turned off — DMP left running, motionTrackingReady never reset.
When control["on"] == false (or when the node is destroyed), the DMP remains enabled and motionTrackingReady stays true, so loop20ms keeps reading. Add an else branch to disable DMP and reset state, and mirror that in the destructor.
Proposed fix sketch
if (control["on"] == true) {
// ... existing init code ...
+ } else {
+ if (motionTrackingReady) {
+ mpu.setDMPEnabled(false);
+ motionTrackingReady = false;
+ }
}And in the destructor:
- ~IMUDriver() override {};
+ ~IMUDriver() override {
+ if (motionTrackingReady) {
+ mpu.setDMPEnabled(false);
+ motionTrackingReady = false;
+ }
+ };🤖 Prompt for AI Agents
In `@src/MoonLight/Nodes/Drivers/D_IMU.h` around lines 38 - 88, Add a cleanup path
for when the driver is turned off by modifying onUpdate in D_IMU: when
control["on"] == false (or control["on"] is present and false) call the MPU
shutdown steps—disable the DMP via mpu.setDMPEnabled(false) (or equivalent),
clear any interrupt/status flags, stop any periodic reads, and set
motionTrackingReady = false; also ensure any board==1 BMI160 cleanup is
mirrored. Then implement the same teardown in the D_IMU destructor (~D_IMU) to
disable DMP/interrupts and reset motionTrackingReady so loop20ms stops reading
after node removal. Ensure you reference the existing symbols onUpdate,
motionTrackingReady, mpu.setDMPEnabled (and ~D_IMU) when making changes.

Summary by CodeRabbit